-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[TT-15863] fix random version picking for not versioned API #7380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TT-15863] fix random version picking for not versioned API #7380
Conversation
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
API Changes --- prev.txt 2025-10-01 12:16:18.556525090 +0000
+++ current.txt 2025-10-01 12:16:09.330488606 +0000
@@ -9141,6 +9141,10 @@
func (s *APISpec) AddUnloadHook(hook func())
AddUnloadHook adds a function to be called when the API spec is unloaded
+func (a *APISpec) CheckForAmbiguousDefaultVersions() bool
+ CheckForAmbiguousDefaultVersions checks if there are multiple ambiguous
+ default versions in the version data.
+
func (a *APISpec) CheckSpecMatchesStatus(r *http.Request, rxPaths []URLSpec, mode URLStatus) (bool, interface{})
CheckSpecMatchesStatus checks if a URL spec has a specific status.
Deprecated: The function doesn't follow go return conventions (T, ok);
@@ -9160,6 +9164,11 @@
takes the precedence. If the global one is `true`, value of the one in api
level doesn't matter.
+func (a *APISpec) GetSingleOrDefaultVersion() (versionInfo apidef.VersionInfo, ok bool)
+ GetSingleOrDefaultVersion determines and returns a single version or the
+ default version if only one or a default exists. Returns versionInfo and a
+ boolean indicating success or failure.
+
func (a *APISpec) GetTykExtension() *oas.XTykAPIGateway
func (a *APISpec) Init(authStore, sessionStore, healthStore, orgStore storage.Handler)
@@ -11395,36 +11404,38 @@
RequestStatus is a custom type to avoid collisions
const (
- VersionNotFound RequestStatus = "Version information not found"
- VersionDoesNotExist RequestStatus = "This API version does not seem to exist"
- VersionWhiteListStatusNotFound RequestStatus = "WhiteListStatus for path not found"
- VersionExpired RequestStatus = "Api Version has expired, please check documentation or contact administrator"
- APIExpired RequestStatus = "API has expired, please check documentation or contact administrator"
- EndPointNotAllowed RequestStatus = "Requested endpoint is forbidden"
- StatusOkAndIgnore RequestStatus = "Everything OK, passing and not filtering"
- StatusOk RequestStatus = "Everything OK, passing"
- StatusCached RequestStatus = "Cached path"
- StatusTransform RequestStatus = "Transformed path"
- StatusTransformResponse RequestStatus = "Transformed response"
- StatusTransformJQ RequestStatus = "Transformed path with JQ"
- StatusTransformJQResponse RequestStatus = "Transformed response with JQ"
- StatusHeaderInjected RequestStatus = "Header injected"
- StatusMethodTransformed RequestStatus = "Method Transformed"
- StatusHeaderInjectedResponse RequestStatus = "Header injected on response"
- StatusRedirectFlowByReply RequestStatus = "Exceptional action requested, redirecting flow!"
- StatusHardTimeout RequestStatus = "Hard Timeout enforced on path"
- StatusCircuitBreaker RequestStatus = "Circuit breaker enforced"
- StatusURLRewrite RequestStatus = "URL Rewritten"
- StatusVirtualPath RequestStatus = "Virtual Endpoint"
- StatusRequestSizeControlled RequestStatus = "Request Size Limited"
- StatusRequestTracked RequestStatus = "Request Tracked"
- StatusRequestNotTracked RequestStatus = "Request Not Tracked"
- StatusValidateJSON RequestStatus = "Validate JSON"
- StatusValidateRequest RequestStatus = "Validate Request"
- StatusInternal RequestStatus = "Internal path"
- StatusGoPlugin RequestStatus = "Go plugin"
- StatusPersistGraphQL RequestStatus = "Persist GraphQL"
- StatusRateLimit RequestStatus = "Rate Limited"
+ VersionNotFound RequestStatus = "Version information not found"
+ VersionDoesNotExist RequestStatus = "This API version does not seem to exist"
+ VersionWhiteListStatusNotFound RequestStatus = "WhiteListStatus for path not found"
+ VersionExpired RequestStatus = "Api Version has expired, please check documentation or contact administrator"
+ VersionDefaultForNotVersionedNotFound RequestStatus = "No default API version for this non-versioned API found"
+ VersionAmbiguousDefault RequestStatus = "Ambiguous default API version for this non-versioned API"
+ APIExpired RequestStatus = "API has expired, please check documentation or contact administrator"
+ EndPointNotAllowed RequestStatus = "Requested endpoint is forbidden"
+ StatusOkAndIgnore RequestStatus = "Everything OK, passing and not filtering"
+ StatusOk RequestStatus = "Everything OK, passing"
+ StatusCached RequestStatus = "Cached path"
+ StatusTransform RequestStatus = "Transformed path"
+ StatusTransformResponse RequestStatus = "Transformed response"
+ StatusTransformJQ RequestStatus = "Transformed path with JQ"
+ StatusTransformJQResponse RequestStatus = "Transformed response with JQ"
+ StatusHeaderInjected RequestStatus = "Header injected"
+ StatusMethodTransformed RequestStatus = "Method Transformed"
+ StatusHeaderInjectedResponse RequestStatus = "Header injected on response"
+ StatusRedirectFlowByReply RequestStatus = "Exceptional action requested, redirecting flow!"
+ StatusHardTimeout RequestStatus = "Hard Timeout enforced on path"
+ StatusCircuitBreaker RequestStatus = "Circuit breaker enforced"
+ StatusURLRewrite RequestStatus = "URL Rewritten"
+ StatusVirtualPath RequestStatus = "Virtual Endpoint"
+ StatusRequestSizeControlled RequestStatus = "Request Size Limited"
+ StatusRequestTracked RequestStatus = "Request Tracked"
+ StatusRequestNotTracked RequestStatus = "Request Not Tracked"
+ StatusValidateJSON RequestStatus = "Validate JSON"
+ StatusValidateRequest RequestStatus = "Validate Request"
+ StatusInternal RequestStatus = "Internal path"
+ StatusGoPlugin RequestStatus = "Go plugin"
+ StatusPersistGraphQL RequestStatus = "Persist GraphQL"
+ StatusRateLimit RequestStatus = "Rate Limited"
)
Statuses of the request, all are false-y except StatusOk and
StatusOkAndIgnore |
🔍 Code Analysis ResultsOf course. Here is a comprehensive overview and analysis of the pull request. 1. Change Impact AnalysisWhat This PR AccomplishesThis pull request addresses a critical bug where the Tyk gateway would select a random, non-deterministic version for an API marked as "not versioned." The previous implementation iterated over a map of versions and picked the first one it encountered. Since map iteration order is not guaranteed in Go, this led to unpredictable behavior. This PR replaces that logic with a predictable and robust mechanism to select a single, default, or implicitly-named default version, ensuring consistent behavior for all requests to non-versioned APIs. Key Technical Changes
Affected System Components
2. Architecture VisualizationThe following flowchart visualizes the updated logic within the flowchart TD
subgraph "APISpec.Version Method (for NotVersioned API)"
A[Incoming Request] --> B{Check for Ambiguous Defaults};
B -- Yes --> C[Return Error: VersionAmbiguousDefault];
B -- No --> D["GetSingleOrDefaultVersion()"];
subgraph "GetSingleOrDefaultVersion Logic"
D --> E{Only one version exists?};
E -- Yes --> F[Return the single version];
E -- No --> G{"Is 'Default' version defined?"};
G -- Yes --> H["Return 'Default' version"];
G -- No --> I{"Is 'default' version defined?"};
I -- Yes --> J["Return 'default' version"];
I -- No --> K{"Is '' (empty string) version defined?"};
K -- Yes --> L["Return '' version"];
K -- No --> M[No default version found];
end
F --> Z["Return Version & StatusOk"];
H --> Z;
J --> Z;
L --> Z;
M --> N[Return Error: VersionDefaultForNotVersionedNotFound];
end
C --> X[End Request Processing];
N --> X;
Z --> X;
Powered by Visor from Probelabs Last updated: 2025-10-01T12:20:01.293Z | Triggered by: synchronize | Commit: 63452ed |
🔍 Code Analysis ResultsSecurity Issues (1)
Performance Issues (1)
Quality Issues (3)
Style Issues (1)
Dependency Issues (1)
✅ Connectivity Check PassedNo connectivity issues found – changes LGTM. Powered by Visor from Probelabs Last updated: 2025-10-01T12:20:02.700Z | Triggered by: synchronize | Commit: 63452ed |
Regarding Visor: The previous behavior of selecting random versions was unintended behavior, therefore it can't be considered a breaking change. |
…r-not-versioned-api
…r-not-versioned-api
|
/release to release-5.10 |
Working on it! Note that it can take a few minutes. |
<details open> <summary><a href="https://tyktech.atlassian.net/browse/TT-15863" title="TT-15863" target="_blank">TT-15863</a></summary> <br /> <table> <tr> <th>Summary</th> <td>Random version being picked when not_versioned is set to true</td> </tr> <tr> <th>Type</th> <td> <img alt="Bug" src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium" /> Bug </td> </tr> <tr> <th>Status</th> <td>In Dev</td> </tr> <tr> <th>Points</th> <td>N/A</td> </tr> <tr> <th>Labels</th> <td>-</td> </tr> </table> </details> <!-- do not remove this marker as it will break jira-lint's functionality. added_by_jira_lint --> --- This PR fixes an issue where a random version was picked for a non-versioned API. Instead of picking a random version for non-versioned APIs it will now: 1. Use the only defined version 2. Use the version called "Default", "default" or "" when multiple versions are present 3. Return error when multiple default versions are found 4. Return error when no version exists (cherry picked from commit 14c6d8f)
@pvormste Seems like there is conflict and it require manual merge. |
/release to releae-5.8 |
@pvormste Release branch not found |
/release to release-5.8 |
Working on it! Note that it can take a few minutes. |
<details open> <summary><a href="https://tyktech.atlassian.net/browse/TT-15863" title="TT-15863" target="_blank">TT-15863</a></summary> <br /> <table> <tr> <th>Summary</th> <td>Random version being picked when not_versioned is set to true</td> </tr> <tr> <th>Type</th> <td> <img alt="Bug" src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium" /> Bug </td> </tr> <tr> <th>Status</th> <td>In Dev</td> </tr> <tr> <th>Points</th> <td>N/A</td> </tr> <tr> <th>Labels</th> <td>-</td> </tr> </table> </details> <!-- do not remove this marker as it will break jira-lint's functionality. added_by_jira_lint --> --- This PR fixes an issue where a random version was picked for a non-versioned API. Instead of picking a random version for non-versioned APIs it will now: 1. Use the only defined version 2. Use the version called "Default", "default" or "" when multiple versions are present 3. Return error when multiple default versions are found 4. Return error when no version exists (cherry picked from commit 14c6d8f)
@pvormste Created merge PRs |
…t versioned API (#7380) (#7394) ### **User description** [TT-15863] fix random version picking for not versioned API (#7380) <details open> <summary><a href="https://tyktech.atlassian.net/browse/TT-15863" title="TT-15863" target="_blank">TT-15863</a></summary> <br /> <table> <tr> <th>Summary</th> <td>Random version being picked when not_versioned is set to true</td> </tr> <tr> <th>Type</th> <td> <img alt="Bug" src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium" /> Bug </td> </tr> <tr> <th>Status</th> <td>In Dev</td> </tr> <tr> <th>Points</th> <td>N/A</td> </tr> <tr> <th>Labels</th> <td>-</td> </tr> </table> </details> <!-- do not remove this marker as it will break jira-lint's functionality. added_by_jira_lint --> --- This PR fixes an issue where a random version was picked for a non-versioned API. Instead of picking a random version for non-versioned APIs it will now: 1. Use the only defined version 2. Use the version called "Default", "default" or "" when multiple versions are present 3. Return error when multiple default versions are found 4. Return error when no version exists [TT-15863]: https://tyktech.atlassian.net/browse/TT-15863?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ ___ ### **PR Type** Bug fix, Tests ___ ### **Description** - Deterministic version selection for non-versioned APIs - New request statuses for default version issues - Helper methods to resolve default/ambiguous versions - Comprehensive unit tests for new logic ___ ### Diagram Walkthrough ```mermaid flowchart LR VersionReq["APISpec.Version(request)"] CheckAmb["CheckForAmbiguousDefaultVersions()"] GetDef["GetSingleOrDefaultVersion()"] Ok["Return StatusOk with version"] ErrAmb["Return VersionAmbiguousDefault"] ErrDef["Return VersionDefaultForNotVersionedNotFound"] VersionReq -- "NotVersioned == true" --> CheckAmb CheckAmb -- "ambiguous" --> ErrAmb CheckAmb -- "not ambiguous" --> GetDef GetDef -- "found" --> Ok GetDef -- "not found" --> ErrDef ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Bug fix</strong></td><td><table> <tr> <td> <details> <summary><strong>api_definition.go</strong><dd><code>Deterministic version resolution and new statuses</code> </dd></summary> <hr> gateway/api_definition.go <ul><li>Add new RequestStatus values for default version errors<br> <li> Implement GetSingleOrDefaultVersion helper<br> <li> Implement CheckForAmbiguousDefaultVersions helper<br> <li> Update Version() to use deterministic selection for non-versioned APIs</ul> </details> </td> <td><a href="https://github.com/TykTechnologies/tyk/pull/7394/files#diff-0cf80174bbafb36f6d4f4308ebbd971b2833b76a936bad568220aa1a4ba0ee8b">+92/-34</a> </td> </tr> </table></td></tr><tr><td><strong>Tests</strong></td><td><table> <tr> <td> <details> <summary><strong>api_definition_test.go</strong><dd><code>Unit tests for version resolution logic</code> </dd></summary> <hr> gateway/api_definition_test.go <ul><li>Add tests for GetSingleOrDefaultVersion<br> <li> Add tests for CheckForAmbiguousDefaultVersions<br> <li> Add tests for Version() with not_versioned scenarios<br> <li> Validate new error statuses and outcomes</ul> </details> </td> <td><a href="https://github.com/TykTechnologies/tyk/pull/7394/files#diff-2394daab6fdc5f8dc234699c80c0548947ee3d68d2e33858258d73a8b5eb6f44">+317/-0</a> </td> </tr> </table></td></tr></tr></tbody></table> </details> ___ Co-authored-by: Patric Vormstein <[email protected]>
… versioned API (#7380) [TT-15863] fix random version picking for not versioned API (#7380) <details open> <summary><a href="https://tyktech.atlassian.net/browse/TT-15863" title="TT-15863" target="_blank">TT-15863</a></summary> <br /> <table> <tr> <th>Summary</th> <td>Random version being picked when not_versioned is set to true</td> </tr> <tr> <th>Type</th> <td> <img alt="Bug" src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium" /> Bug </td> </tr> <tr> <th>Status</th> <td>In Dev</td> </tr> <tr> <th>Points</th> <td>N/A</td> </tr> <tr> <th>Labels</th> <td>-</td> </tr> </table> </details> <!-- do not remove this marker as it will break jira-lint's functionality. added_by_jira_lint --> --- This PR fixes an issue where a random version was picked for a non-versioned API. Instead of picking a random version for non-versioned APIs it will now: 1. Use the only defined version 2. Use the version called "Default", "default" or "" when multiple versions are present 3. Return error when multiple default versions are found 4. Return error when no version exists
TT-15863
This PR fixes an issue where a random version was picked for a non-versioned API.
Instead of picking a random version for non-versioned APIs it will now: